Skip to content

fix: carry the IDE entry's env when wiring the datamate stdio MCP server - #1081

Merged
ralphstodomingo merged 19 commits into
mainfrom
fix/datamate-stdio-env
Sep 8, 2026
Merged

fix: carry the IDE entry's env when wiring the datamate stdio MCP server#1081
ralphstodomingo merged 19 commits into
mainfrom
fix/datamate-stdio-env

Conversation

@ralphstodomingo

@ralphstodomingo ralphstodomingo commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Issue for this PR

Closes #1082

Type of change

  • Bug fix

What does this PR do?

Fixes the bug where datamate-cli.js suddenly opens as an editor tab when launching sessions, and the datamate MCP server dies with -32000 Connection closed.

On desktop editors the extension-written .vscode/mcp.json datamate stdio entry has command = the editor's Electron binary and env: {"ELECTRON_RUN_AS_NODE": "1"} (Electron only runs the script as Node with that flag; without it, the editor GUI boots and opens the script as a document). datamate_manager add reused the entry's command + args but dropped the env block, both in the immediate spawn and in the entry persisted to .altimate-code/altimate-code.json — so the file popped on add and again on every later session launch, with no self-repair in TUI/run (the healing sync only ran on serve boot).

Changes:

  • readDatamateTransportFromIde now returns the IDE entry's env (minus ALTIMATE_EXTENSION_RPC, mirroring the sync path) and updatedAt; handleAdd carries the env into the runtime MCP config and persists it as environment, plus updatedAt on disk so the sync recognizes the entry as current.
  • The sync path's inline env-strip is extracted into a shared extractSpawnEnvironment helper so the two paths stay in lockstep.
  • The TUI worker and run now run syncDatamateUrlFromVscodeMcp before the first session, as serve already did — entries already persisted broken in the field self-heal on the next launch. The heal is scoped to the containing git project root (resolveDatamateSyncRoot, bounded at the home directory), walks the config files from the launch directory up to that root the way the loader does (a nested package's own config is healed too), and in the worker it is sequenced strictly before config load, the first in-process request, and Server.listen, so the first session connects with the healed entry rather than a stale cached one. datamate_manager add on an existing-but-disconnected entry likewise refreshes it from the current IDE transport before connecting.
  • Trust boundary (from review): the env carry is an allowlistELECTRON_RUN_AS_NODE only, since the carried env is spread over the host process env at spawn. Transport sources are only the two locations the extension writes (**/.vscode/mcp.json, **/.cursor/mcp.json), parsed through a validating parseIdeTransport (local needs a non-empty command, remote a non-empty url; blank tombstones and incomplete entries are skipped rather than winning selection — an incomplete entry used to be persisted as a url-less remote). Entries derived from an IDE file carry provenance (managedBy: "altimate-ide" + sourceMcpJson), and the boot heal never rewrites a global entry from a project file unless that entry's stamp matches the exact IDE file — hand-added and legacy global entries are left alone; an explicit datamate_manager add is what (re)stamps them and is the remedy for a legacy global entry.
  • Scope note: everything above is datamate-specific except one known side effect of the wider sync trigger — syncDatamateUrlFromVscodeMcp has a second pass that refreshes the URL (and updatedAt) of other remote MCP entries mirrored from the IDE config (name match, URL differs). That pass is not new behavior — serve boot has always run it — TUI/run now just apply the same refresh consistently. Spawn/env behavior for non-datamate servers is unchanged.

How did you verify your code works?

E2E in the docker code-server harness against a desktop-shaped mcp.json entry (command = an Electron-contract shim that opens its args as documents unless ELECTRON_RUN_AS_NODE=1), driven through real run sessions:

Scenario Published 0.8.10 This branch
datamate_manager add (project) file pops, -32000 Connection closed, env-less entry persisted no pop, connected as 'datamate', entry carries environment + provenance
Plain launch on the persisted entry file pops on every launch
Broken project entry, plain launch healed before connect, no pop
Broken global entry stamped from this project's mcp.json, plain launch healed, no pop
Legacy/hand-added global entry (no provenance), plain launch left untouched by design (pops until the explicit add below)
Explicit datamate_manager add --scope global on that legacy entry, then relaunch entry restamped + healed, relaunch is clean
Blanked .cursor/mcp.json tombstone sorting first healed, no pop
Published 0.8.10 launched on a healed entry no pop (heal is one-way — a reporter who tested a fix build can no longer reproduce)

Unit tests: test/release-validation/mcp-datamate-stdio-env.test.ts covers the env carry (strip rule, omission when empty, back-compat bare shape, non-string filtering) and sync parity. Existing mcp-datamate-893 suite unchanged and green; tsgo --noEmit clean.

Screenshots / recordings

Before — datamate_manager add pops the file open:

before

After — same broken persisted entry, next session heals it and nothing pops:

after

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

Review contract (claims + residuals) lives in the pinned review-log comment.

`datamate_manager add` reused the command + args from the IDE's `mcp.json`
`datamate` entry but dropped its `env` block, both in the immediate spawn and
in the entry persisted to `.altimate-code/altimate-code.json`. On desktop
editors the command is the editor's Electron binary and `env` carries
`ELECTRON_RUN_AS_NODE=1` — spawned without it, the editor GUI boots and opens
`datamate-cli.js` as a document, the MCP client reports `-32000 Connection
closed`, and the broken persisted entry re-pops the file on every subsequent
session launch.

- `readDatamateTransportFromIde` now returns the entry's env (minus
  `ALTIMATE_EXTENSION_RPC`, mirroring the sync path) and `updatedAt`;
  `handleAdd` carries the env into the runtime config and persists it as
  `environment`, plus `updatedAt` on disk so the sync recognizes the entry
  as current.
- The sync path's inline env-strip is extracted into the shared
  `extractSpawnEnvironment` helper so both paths stay in lockstep.
- The TUI worker and `run` now run `syncDatamateUrlFromVscodeMcp` before the
  first session (as `serve` already did), so entries already persisted
  without `environment` self-heal on the next launch.
@ralphstodomingo ralphstodomingo self-assigned this Aug 7, 2026
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Datamate local transports now preserve filtered environment variables and updatedAt. Run and TUI startup paths synchronize VS Code MCP configuration before use. Regression tests cover environment filtering, Git-root resolution, and persisted transport metadata.

Changes

Datamate synchronization

Layer / File(s) Summary
Transport metadata and discovery
packages/opencode/src/altimate/datamate-transport.ts
Local and remote transports support updatedAt. Local transports support filtered environment values. Discovery and synchronization preserve valid values and resolve the Git project root when available.
Configuration synchronization and persistence
packages/opencode/src/altimate/datamate-transport.ts, packages/opencode/src/altimate/tools/datamate.ts
Synchronization preserves filtered environment values and timestamps. Existing entries retain non-transport fields, set enabled: true, write refreshed configuration, and reconnect with MCP.add().
Startup synchronization and validation
packages/opencode/src/cli/cmd/run.ts, packages/opencode/src/cli/tui/worker.ts, packages/opencode/test/release-validation/mcp-datamate-stdio-env.test.ts, packages/opencode/test/release-validation/mcp-datamate-893.test.ts
Run and TUI paths perform best-effort synchronization before startup, RPC fetches, and external server startup. Tests cover environment conversion, root resolution, global configuration, and persisted metadata.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Suggested reviewers: anandgupta42

Sequence Diagram(s)

sequenceDiagram
  participant RunCommand
  participant DatamateTransport
  participant MCPConfig
  participant DatamateGateway
  RunCommand->>DatamateTransport: resolve project root
  RunCommand->>MCPConfig: synchronize Datamate entry
  MCPConfig->>DatamateTransport: read command, environment, and updatedAt
  DatamateTransport-->>MCPConfig: return filtered transport metadata
  MCPConfig->>DatamateGateway: persist refreshed entry
  DatamateGateway-->>RunCommand: complete or suppress synchronization error
  RunCommand->>DatamateGateway: start local session
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes satisfy issue #1082 by preserving the IDE environment, repairing persisted entries, and synchronizing before session startup.
Out of Scope Changes check ✅ Passed The changes remain within Datamate transport synchronization and environment handling, including the stated global configuration support.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check ✅ Passed The title clearly identifies the primary fix: preserving the IDE entry environment when wiring the Datamate stdio MCP server. It is concise and related to the main change.
Description check ✅ Passed The description includes the issue reference, change type, detailed implementation rationale, verification results, screenshots, and completed checklist. It also documents scope and known residual beh…
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/datamate-stdio-env

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

A rabbit keeps the Node flag bright,
Filters stray variables from sight.
Timestamps hop into the stream,
Startup entries heal the scheme.
MCP runs without surprise.

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown

Thanks for updating your PR! It now meets our contributing guidelines. 👍

@AltimateAI AltimateAI deleted a comment from github-actions Bot Aug 7, 2026
@ralphstodomingo
ralphstodomingo marked this pull request as ready for review August 7, 2026 04:26
Copilot AI review requested due to automatic review settings August 7, 2026 04:26

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review for a one-time review, or @claude review always to subscribe this PR to a review on every future push.

Tip: disable this comment in your organization's Code Review settings.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a desktop-editor regression where the IDE-provided datamate stdio MCP entry’s env (notably ELECTRON_RUN_AS_NODE=1) was dropped when wiring/persisting the server, causing Electron to boot the editor UI and open datamate-cli.js as a tab, and leading to -32000 Connection closed. It also expands the “heal from .vscode/mcp.json” sync behavior so terminal entrypoints (run/TUI worker) self-repair already-persisted broken entries, matching serve startup behavior.

Changes:

  • Carry the IDE env (minus ALTIMATE_EXTENSION_RPC) and updatedAt through readDatamateTransportFromIde, datamate_manager add runtime wiring, and persisted config.
  • Deduplicate env-stripping logic into a shared extractSpawnEnvironment() helper to keep add and sync paths aligned.
  • Trigger syncDatamateUrlFromVscodeMcp earlier for run and the TUI worker so previously-broken persisted entries self-heal on next launch.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
packages/opencode/src/altimate/datamate-transport.ts Adds env + updatedAt propagation for IDE datamate stdio entries; factors env normalization into extractSpawnEnvironment; updates sync to use shared env extraction.
packages/opencode/src/altimate/tools/datamate.ts Ensures datamate_manager add carries environment into runtime MCP config and persists environment + updatedAt to disk.
packages/opencode/src/cli/tui/worker.ts Adds a boot-time datamate sync gate so the worker doesn’t serve requests / start external server mode until the heal attempt finishes.
packages/opencode/src/cli/cmd/run.ts Runs the same datamate sync before bootstrapping a session to self-heal env-less persisted entries.
packages/opencode/test/release-validation/mcp-datamate-stdio-env.test.ts Adds regression coverage for env carry-through, stripping rules, back-compat, and sync parity.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/opencode/src/cli/tui/worker.ts Outdated
@kilo-code-bot

kilo-code-bot Bot commented Aug 7, 2026

Copy link
Copy Markdown

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Files Reviewed (2 files)
  • packages/opencode/src/altimate/datamate-transport.ts
  • packages/opencode/test/release-validation/mcp-datamate-stdio-env.test.ts
Previous Review Summaries (17 snapshots, latest commit 63b489a)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 63b489a)

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 1
Issue Details (click to expand)

SUGGESTION

File Line Issue
packages/opencode/src/altimate/datamate-transport.ts 267 Redundant dir === rootResolved in the negated break clause — simplifies to `dir === rootResolved
Files Reviewed (3 files)
  • packages/opencode/src/altimate/datamate-transport.ts - 1 issue
  • packages/opencode/src/altimate/tools/datamate.ts
  • packages/opencode/test/release-validation/mcp-datamate-stdio-env.test.ts

Fix these issues in Kilo Cloud

Previous review (commit b83a8cb)

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 1
Issue Details (click to expand)

SUGGESTION

File Line Issue
packages/opencode/src/altimate/tools/datamate.ts 326 managedBy/sourceMcpJson are already in TRANSPORT_IDENTITY_FIELDS, so re-listing them is redundant
Files Reviewed (2 files)
  • packages/opencode/src/altimate/datamate-transport.ts
  • packages/opencode/src/altimate/tools/datamate.ts - 1 issue

Fix these issues in Kilo Cloud

Previous review (commit 04daa04)

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 1
Issue Details (click to expand)

SUGGESTION

File Line Issue
packages/opencode/src/altimate/datamate-transport.ts 243 collectDatamateHealPaths re-resolves the sync root, so syncDatamateUrlFromVscodeMcp now computes resolveDatamateSyncRoot(launchDir) twice per sync
Files Reviewed (4 files)
  • packages/opencode/src/altimate/datamate-transport.ts - 1 issue
  • packages/opencode/src/server/server.ts
  • packages/opencode/test/release-validation/mcp-datamate-stdio-env.test.ts
  • packages/opencode/test/upstream/adversarial/upi-config-mcp.test.ts

Fix these issues in Kilo Cloud

Previous review (commit bdce412)

Status: No Issues Found | Recommendation: Merge

Incremental review of 01124f12..bdce4125 — commit bdce4125c ("fix: refresh connected entries on transport change, share the restamp merge, tidy docs and win32 test skip").

The prior review's only SUGGESTION is resolved: the connected-entry provenance stamp and the disconnected refresh path now share a single mergeRefreshedEntry helper (datamate.ts:194) — one replacedFields exclusion set and one merge order, so the two paths can no longer drift. The connected path additionally broadens its disk-update trigger from provenance-only to any transport-identity/provenance change (identityChanged at datamate.ts:324), closing the stale-spawn gap for connected entries whose command/env changed under matching provenance. The datamate-transport.ts change is a doc-comment relocation only (the resolver JSDoc is reattached to its declaration), and the test change routes the home-via-symlink test through the suite-standard testSymlink win32 skip.

Files Reviewed (3 files)
  • packages/opencode/src/altimate/datamate-transport.ts
  • packages/opencode/src/altimate/tools/datamate.ts
  • packages/opencode/test/release-validation/mcp-datamate-stdio-env.test.ts

Previous review (commit 01124f1)

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 1
Issue Details (click to expand)

SUGGESTION

File Line Issue
packages/opencode/src/altimate/tools/datamate.ts 299 Provenance-stamp block duplicates the refresh path's preserve-and-merge logic
Files Reviewed (2 files)
  • packages/opencode/src/altimate/datamate-transport.ts
  • packages/opencode/src/altimate/tools/datamate.ts - 1 issue

Fix these issues in Kilo Cloud

Previous review (commit e96f26b)

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
packages/opencode/src/altimate/datamate-transport.ts 41 Transport scan narrowed to .vscode/.cursor, dropping .github/copilot/mcp.json which the tool's docs and mcp/discover.ts still list as a supported IDE location
Files Reviewed (4 files)
  • packages/opencode/src/altimate/datamate-transport.ts - 1 issue
  • packages/opencode/src/altimate/tools/datamate.ts
  • packages/opencode/src/mcp/config.ts
  • packages/opencode/test/release-validation/mcp-datamate-893-codex.test.ts
  • packages/opencode/test/release-validation/mcp-datamate-893.test.ts
  • packages/opencode/test/release-validation/mcp-datamate-stdio-env.test.ts

Fix these issues in Kilo Cloud

Previous review (commit 692417a)

Status: No Issues Found | Recommendation: Merge

Incremental review of 9b167348..692417ac — commit 692417ac5 ("refactor: share the blank-tombstone predicate between both mcp.json scans").

The commit resolves the prior review's only SUGGESTION: the duplicated blank-tombstone predicate is now a single isBlankDatamateEntry helper (datamate-transport.ts:53), used by both scan sites. readDatamateTransportFromIde (line 166) skips blank entries via if (isBlankDatamateEntry(entry)) continue, and syncDatamateUrlFromVscodeMcp (line 245) selects a non-blank source via if (!isBlankDatamateEntry(map[DATAMATE_KEY])) — the same logic as before, in lockstep. The helper additionally guards typeof entry !== "object", a strictly more defensive (and correct) check since a non-object value can never be a valid MCP server entry. Behavior is otherwise identical; no drift risk remains.

Files Reviewed (1 file)
  • packages/opencode/src/altimate/datamate-transport.ts

Previous review (commit 9b16734)

Status: 1 Issue Found | Recommendation: Address before merge

Incremental review of 7f684289..9b167348 — commit 9b1673481 ("fix: skip blanked {} datamate entries when selecting the mcp.json source").

The extension blanks datamate to {} (a tombstone) in non-active-IDE mcp.json files, and the sorted scan can reach the blanked file first (.cursor/ sorts before .vscode/). Both scan sites — readDatamateTransportFromIde (line 159) and syncDatamateUrlFromVscodeMcp (line 239) — now skip empty entries so the active IDE's real entry is found. Previously a blanked entry short-circuited the read scan (returning a fallback marker) and made the sync silently no-op while selecting the wrong file. The fix is correct and well-covered by two new tests. One minor maintainability nit on the duplicated predicate.

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 1
Issue Details (click to expand)

SUGGESTION

File Line Issue
packages/opencode/src/altimate/datamate-transport.ts 239 Blank-tombstone predicate duplicated at both scan sites
Files Reviewed (2 files)
  • packages/opencode/src/altimate/datamate-transport.ts - 1 issue
  • packages/opencode/test/release-validation/mcp-datamate-stdio-env.test.ts

Fix these issues in Kilo Cloud

Previous review (commit 7f68428)

Status: No Issues Found | Recommendation: Merge

Incremental review of 42311f23..7f684289 — commit 7f6842891 ("test: update reload-endpoint source guard for the multi-path disk read").

Test-only change: the adversarial UPI-25..27 suite's source-text guard for the /altimate/mcp/reload-datamate endpoint is updated to match the multi-path disk read that landed in earlier commits. The old single-path assertion (const freshEntry = await readMcpEntryFromDisk(name, configPath)) was already stale — the shipped endpoint scans every config file via findAllConfigPaths(directory, Global.Path.config) and loops until it finds the entry (server.ts:694–712). The new assertions (const configPaths = await findAllConfigPaths(...), freshEntry = await readMcpEntryFromDisk(name, configPath), await MCP.add(name, freshEntry)) were each verified verbatim against the current source. The stale-singleton bypass contract asserted by the surrounding checks is unchanged. No issues on changed lines.

Files Reviewed (1 file)
  • packages/opencode/test/upstream/adversarial/upi-config-mcp.test.ts

Previous review (commit 42311f2)

Status: No Issues Found | Recommendation: Merge

Incremental review of 6625177f..42311f23 — commit 42311f23 ("scope legacy config.json to global config candidates only").

The split is correct: config/config.ts loadGlobal merges config.json from the global dir (config.ts:360), but the project loader (ConfigPaths.files searches only opencode.json{,c} at paths.ts:24, and the .altimate-code/.opencode loop reads only altimate-code.json{,c} + opencode.json{,c} at config.ts:538-545) never reads a project-level config.json. Gating config.json on the global scope in both resolveConfigPath and findAllConfigPaths stops the heal from discovering/writing an entry the loader would ignore, and the default write target is unaffected (config.json was the tail candidate). Covered by a byte-identity regression test. No issues on changed lines.

Files Reviewed (2 files)
  • packages/opencode/src/mcp/config.ts
  • packages/opencode/test/release-validation/mcp-datamate-stdio-env.test.ts

Previous review (commit 6625177)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (6 files)
  • packages/opencode/src/altimate/datamate-transport.ts
  • packages/opencode/src/cli/cmd/run.ts
  • packages/opencode/src/cli/tui/worker.ts
  • packages/opencode/src/mcp/config.ts
  • packages/opencode/src/server/server.ts
  • packages/opencode/test/release-validation/mcp-datamate-stdio-env.test.ts

Previous review (commit 33b60d8)

Status: 1 Issue Found | Recommendation: Merge (1 non-blocking suggestion)

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 1
Issue Details (click to expand)

SUGGESTION

File Line Issue
packages/opencode/src/altimate/datamate-transport.ts 331 A throw on one config file aborts healing of the rest
Files Reviewed (3 files)
  • packages/opencode/src/altimate/datamate-transport.ts - 1 suggestion
  • packages/opencode/test/release-validation/mcp-datamate-893.test.ts
  • packages/opencode/test/release-validation/mcp-datamate-stdio-env.test.ts

The incremental commit (37c3d2c..33b60d8) extends the datamate heal to run across every config file (project + global) instead of just the project one, via findAllConfigPaths(cwd, globalConfigDir) and an extracted healEntryInFile helper. The global-dir default (Global.Path.config) matches the convention used by the sibling callers; the new "already up to date" / "synced" logs now include configPath; and updated reports DATAMATE_KEY once even when multiple files are healed. Tests cover global-only and project+global-in-one-pass healing. The only finding is a non-blocking robustness suggestion on the new loop.

Fix these issues in Kilo Cloud

Previous review (commit 37c3d2c)

Status: No Issues Found | Recommendation: Merge

The incremental commit (37c3d2c) hoists the duplicated updatedAt conditional spread from both handleAdd branches into a single shared updatedAtField constant computed once at the top of the IDE/extension-mode block. This is a clean, behavior-preserving refactor that resolves the prior DRY suggestion. transport.updatedAt (non-null inside the transport !== null branch) replaces the now-redundant transport?.updatedAt optional chaining, and the explanatory comment was consolidated at the declaration site.

Files Reviewed (1 file)
  • packages/opencode/src/altimate/tools/datamate.ts

Previous review (commit 7bcc9b6)

Status: 1 Issue Found | Recommendation: Merge (non-blocking suggestion)

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 1
Issue Details (click to expand)

SUGGESTION

File Line Issue
packages/opencode/src/altimate/tools/datamate.ts 284 Duplicated updatedAt conditional spread in both handleAdd branches (also at L303)

The incremental commit (7bcc9b6) correctly extends the datamate env/transport fix to remote transports and fixes a real inconsistency where the live MCP.add client dropped preserved auth/connection settings that the disk write kept. Verified sound:

  • DatamateTransport's remote variant now carries updatedAt?, and readDatamateTransportFromIde returns it for remote entries — parity with the local branch.
  • Both handleAdd updatedAt conditions generalize from transport?.type === "local" && … to transport?.updatedAt, matching the type change.
  • The refresh path's MCP.add now receives the merged refreshed entry instead of the bare mcpConfig. create() only short-circuits on enabled === false, so enabled: true connects exactly as before, and updatedAt/enabled are harmless extra keys in the in-memory s.config (not schema-validated at add, and the disk write is already handled separately by addMcpToConfig).

Only a minor DRY suggestion remains.

Fix these issues in Kilo Cloud

Files Reviewed (3 files)
  • packages/opencode/src/altimate/datamate-transport.ts
  • packages/opencode/src/altimate/tools/datamate.ts
  • packages/opencode/test/release-validation/mcp-datamate-stdio-env.test.ts

Previous review (commit 1cb8fad)

Status: No Issues Found | Recommendation: Merge

The incremental commit (1cb8fad) is a focused refactor that extracts the previously-duplicated TRANSPORT_FIELDS set into a single shared, exported TRANSPORT_IDENTITY_FIELDS constant in datamate-transport.ts, consumed by both syncDatamateUrlFromVscodeMcp and datamate_manager add's refresh path. This directly resolves the prior review's only SUGGESTION (drift risk between the two local sets).

Behavior is verified identical at both call sites:

  • Sync path: old set {type, command, args, environment, url, updatedAt}TRANSPORT_IDENTITY_FIELDS (same 6 fields).
  • handleAdd refresh: old set {…6 fields…, enabled}new Set([...TRANSPORT_IDENTITY_FIELDS, "enabled"]) (same 7 fields).

No new issues introduced; the enabled-added-locally rationale is documented inline.

Files Reviewed (2 files)
  • packages/opencode/src/altimate/datamate-transport.ts
  • packages/opencode/src/altimate/tools/datamate.ts

Previous review (commit 80d4ad4)

Status: 1 Issue Found | Recommendation: Merge (non-blocking)

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 1

The incremental changes (commit 80d4ad4c) correctly resolve the prior review's concerns: the heal is now scoped to the git project root (resolveDatamateSyncRoot) so subdirectory launches find the IDE config + persisted entry, the TUI worker sequences the heal strictly before InstanceRuntime.load/Config.get() (removing the concurrent read/write window), and the in-config-but-not-connected branch now refreshes the persisted entry from the current IDE transport via the established readMcpEntryFromDisk + MCP.add pattern (matching the reload-datamate endpoint) before reconnecting. The primary local-stdio ELECTRON_RUN_AS_NODE fix is sound. Only one minor maintainability nit below.

Issue Details (click to expand)

SUGGESTION

File Line Issue
packages/opencode/src/altimate/tools/datamate.ts 273 TRANSPORT_FIELDS duplicates the set in datamate-transport.ts:258; drift risk
Files Reviewed (5 files)
  • packages/opencode/src/altimate/datamate-transport.ts
  • packages/opencode/src/altimate/tools/datamate.ts
  • packages/opencode/src/cli/cmd/run.ts
  • packages/opencode/src/cli/tui/worker.ts
  • packages/opencode/test/release-validation/mcp-datamate-stdio-env.test.ts

Fix these issues in Kilo Cloud

Previous review (commit cbf4f65)

Status: No Issues Found | Recommendation: Merge

The fix correctly carries the IDE mcp.json env block (notably ELECTRON_RUN_AS_NODE) through both the datamate_manager add path and the mcp.json sync path. The refactor extracts a shared extractSpawnEnvironment helper that is behaviorally equivalent to the prior inline strip for normal cases while additionally filtering non-string values and validating the object shape — a strict, non-regressing improvement. updatedAt is persisted disk-only in handleAdd, matching how syncDatamateUrlFromVscodeMcp already records it, and the new TUI/run heal is awaited before the first session/connect in the correct order using process.cwd() consistently. Fork-only files need no altimate_change markers, and the run.ts/worker.ts additions are correctly wrapped. The new test uses await using tmpdir() (proper disposal) and covers the strip rule, empty-env omission, back-compat bare shape, non-string filtering, and sync parity.

Files Reviewed (5 files)
  • packages/opencode/src/altimate/datamate-transport.ts
  • packages/opencode/src/altimate/tools/datamate.ts
  • packages/opencode/src/cli/cmd/run.ts
  • packages/opencode/src/cli/tui/worker.ts
  • packages/opencode/test/release-validation/mcp-datamate-stdio-env.test.ts

Reviewed by deepseek-v4-pro · Input: 38.5K · Output: 9.4K · Cached: 306K

Review guidance: REVIEW.md from base branch main

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 5 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/opencode/src/cli/tui/worker.ts
Comment thread packages/opencode/src/altimate/tools/datamate.ts
Comment thread packages/opencode/src/altimate/datamate-transport.ts Outdated
Comment thread packages/opencode/src/cli/cmd/run.ts
Comment thread packages/opencode/src/cli/cmd/run.ts
Comment thread packages/opencode/src/cli/tui/worker.ts
Comment thread packages/opencode/src/cli/tui/worker.ts
…root sync scope

- TUI worker: the datamate heal is now sequenced strictly before
  `InstanceRuntime.load`/`Config.get()` (trace init awaits it), so the config
  read can neither race the non-atomic write nor cache the pre-heal entry —
  the first session connects with the healed config.
- `datamate_manager add`: the in-config-but-not-connected branch refreshes the
  persisted entry from the current IDE transport (preserving user-managed
  fields) and connects via `MCP.add`, instead of `MCP.connect` which re-reads
  the stale in-memory entry.
- Boot heals (`run`, TUI worker) scan from the containing git project root via
  the new `resolveDatamateSyncRoot`, not raw cwd — a session launched from a
  subdirectory now finds the root IDE config and persisted entry.
Comment thread packages/opencode/src/altimate/tools/datamate.ts Outdated
…dd refresh

Both paths encode the same idea — entry fields re-derived from the IDE
transport versus user-managed fields carried forward. A single exported set
keeps them from silently diverging when a new transport field is added;
the add-refresh path layers `enabled` on top since it re-derives that too.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 5 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/opencode/src/altimate/tools/datamate.ts Outdated
Comment thread packages/opencode/src/altimate/tools/datamate.ts Outdated
Comment thread packages/opencode/src/altimate/tools/datamate.ts Outdated
…rry updatedAt for remote

- The add-refresh path wrote the merged entry (preserved headers/oauth/timeout
  + fresh transport) to disk but connected the live client with the bare
  transport config, dropping authentication and connection settings for the
  session being connected. MCP.add now receives the same merged entry as the
  disk write, matching the reload-datamate endpoint.
- The remote transport variant now carries updatedAt like the local one, so a
  remote datamate added via datamate_manager is not rewritten once by the next
  boot's sync purely for the missing change signal.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/opencode/src/altimate/datamate-transport.ts (1)

277-284: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Serialize MCP config writes before this sync path.

addMcpToConfig reads mcpConfig, then writes with modify + Filesystem.write without a lock. Concurrent datamate_manager add writes to the same server can overwrite newer fields such as environment, updatedAt, or user-managed headers/oauth/timeout. Add a per-config-path lock or update queue that covers IDE sync and datamate_manager add, and keep the lock shared when resolveConfigPath points to the same file.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/opencode/src/altimate/datamate-transport.ts` around lines 277 - 284,
Serialize the read-modify-write flow in addMcpToConfig with a per-config-path
lock or update queue covering both IDE synchronization and datamate_manager add
operations. Ensure resolveConfigPath results sharing the same file reuse the
same lock, and hold it through mcpConfig reads, modify, and Filesystem.write so
newer environment, updatedAt, headers, oauth, and timeout fields are not
overwritten.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/opencode/src/altimate/datamate-transport.ts`:
- Around line 23-24: Update syncDatamateUrlFromVscodeMcp to compare the datamate
entry’s TRANSPORT_IDENTITY_FIELDS whenever readDatamateTransportFromIde returns
a transport without vscodeUpdatedAt, while preserving timestamp-based
synchronization when the timestamp is present. Add a regression test covering a
timestamp-less IDE transport and verifying that altimate-code.json is
synchronized.

---

Outside diff comments:
In `@packages/opencode/src/altimate/datamate-transport.ts`:
- Around line 277-284: Serialize the read-modify-write flow in addMcpToConfig
with a per-config-path lock or update queue covering both IDE synchronization
and datamate_manager add operations. Ensure resolveConfigPath results sharing
the same file reuse the same lock, and hold it through mcpConfig reads, modify,
and Filesystem.write so newer environment, updatedAt, headers, oauth, and
timeout fields are not overwritten.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 5cbe1fe5-948b-4030-829b-bd6729445c96

📥 Commits

Reviewing files that changed from the base of the PR and between 80d4ad4 and 7bcc9b6.

📒 Files selected for processing (3)
  • packages/opencode/src/altimate/datamate-transport.ts
  • packages/opencode/src/altimate/tools/datamate.ts
  • packages/opencode/test/release-validation/mcp-datamate-stdio-env.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/opencode/test/release-validation/mcp-datamate-stdio-env.test.ts
  • packages/opencode/src/altimate/tools/datamate.ts

Comment thread packages/opencode/src/altimate/datamate-transport.ts Outdated
@ralphstodomingo

Copy link
Copy Markdown
Contributor Author

Re CodeRabbit's outside-diff finding (serialize addMcpToConfig writes): valid hardening suggestion, but the lock-free read-modify-write predates this PR — sync (serve boot + reload endpoint) and datamate_manager add have always been able to interleave across processes. Within a process this PR makes ordering stricter, not looser: the boot heal is sequenced before the first session, so it cannot run concurrently with a session-invoked add. A per-config-path write queue is parked as a follow-up rather than grown into this fix.

Comment thread packages/opencode/src/altimate/tools/datamate.ts Outdated
ralphstodomingo added 2 commits August 7, 2026 14:01
Both the refresh and new-entry branches persisted the transport's updatedAt
with the same conditional spread; a single `updatedAtField` above the branch
keeps them from drifting, and the disk-only rationale is documented once.
datamate_manager add supports scope "global", so a broken (env-less)
datamate entry can live in the global altimate-code.json. It is spawned at
session start like any merged config entry — reproducing the editor-tab pop —
but the boot heal only rewrote the project config, so the entry never
repaired (found by the bug reporter testing the fix: no environment block
appeared). syncDatamateUrlFromVscodeMcp now heals every config file carrying
a datamate entry via findAllConfigPaths (project, project subdirs, global),
reporting the entry once. Sync tests pass an isolated global dir so test runs
never touch the developer's real config.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/opencode/src/altimate/datamate-transport.ts`:
- Around line 331-332: Resolve the Git project root once at the start of
syncDatamateUrlFromVscodeMcp, then use that root instead of cwd for both
findAllMcpJsonFiles and findAllConfigPaths. Add a direct regression test
invoking syncDatamateUrlFromVscodeMcp from a nested directory and verify
root-level configuration files are synchronized.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: edd49936-8519-4d1c-ac2f-62ff387826f9

📥 Commits

Reviewing files that changed from the base of the PR and between 37c3d2c and 33b60d8.

📒 Files selected for processing (3)
  • packages/opencode/src/altimate/datamate-transport.ts
  • packages/opencode/test/release-validation/mcp-datamate-893.test.ts
  • packages/opencode/test/release-validation/mcp-datamate-stdio-env.test.ts

Comment thread packages/opencode/src/altimate/datamate-transport.ts Outdated
@ralphstodomingo

Copy link
Copy Markdown
Contributor Author

Field testing by the bug reporter surfaced a second gap, fixed in 33b60d8: datamate_manager add supports scope: "global", and a broken entry living in the global config (~/.config/altimate-code/altimate-code.json) is spawned at session start like any merged entry — reproducing the pop — but the boot heal only rewrote the project config, so the reporter saw no environment block appear. The sync now heals every config file carrying a datamate entry (findAllConfigPaths: project, project subdirs, global). Covered by new unit tests (global-only and project+global in one pass, with an isolated global dir so test runs never touch the developer's real config) and re-verified end-to-end in the code-server harness: a globally-scoped broken entry now gains environment at boot and nothing pops.

Comment thread packages/opencode/src/altimate/datamate-transport.ts Outdated
…t rejection, correct stale scan docs

Three review findings on the provenance rework:

- The already-connected early return in `datamate_manager add` skipped the
  provenance stamp entirely, so a legacy global entry that happened to be
  connected could never be repaired — the boot heal rejects unstamped global
  entries and the documented explicit-add remedy was a no-op in exactly that
  state. The stamp (plus fresh transport) is now persisted to disk before the
  early return; the live client stays untouched and the healed entry applies
  from the next session.
- The home-root rejection compared lexical paths; a `$HOME` reached through a
  symlink (or differing Windows casing) slipped past it and made the whole
  home tree the "project". Both sides are now canonicalized via realpath
  (case-insensitive on win32), with a regression test using a symlinked
  OPENCODE_TEST_HOME.
- The datamate_manager doc comment still named `.github/copilot/mcp.json` as
  a scanned location; the transport scan deliberately covers only the
  extension-written `.vscode`/`.cursor` files (provenance boundary), while
  generic discovery keeps surfacing the Copilot file for opt-in. The comment
  now says exactly that.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GSk1Tcr4pH3aZRU4tVKrdi

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 3 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread packages/opencode/src/altimate/tools/datamate.ts Outdated
Comment thread packages/opencode/src/altimate/datamate-transport.ts
Comment thread packages/opencode/test/release-validation/mcp-datamate-stdio-env.test.ts Outdated
Comment thread packages/opencode/src/altimate/tools/datamate.ts Outdated
… merge, tidy docs and win32 test skip

- The connected-entry disk update triggered only on provenance mismatch, so a
  changed IDE transport under matching provenance kept spawning stale
  command/env — and entries without updatedAt are skipped by the boot sync,
  making this path their only repair. The write now triggers whenever any
  transport-identity or provenance field differs from the freshly merged
  entry.
- The connected stamp and the disconnected refresh now share one
  mergeRefreshedEntry helper (single exclusion set and merge order).
- isSamePath moved above resolveDatamateSyncRoot's JSDoc so the resolver
  keeps its documentation.
- The symlinked-home regression test routes through the suite-standard
  win32 skip (directory symlinks need elevated privileges there).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GSk1Tcr4pH3aZRU4tVKrdi
@ralphstodomingo

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 7, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-07T10:46:03.762720Z 04daa04 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 3 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread packages/opencode/src/altimate/tools/datamate.ts Outdated
@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown

👋 This PR was automatically closed by our quality checks.

Common reasons:

  • New GitHub account with limited contribution history
  • PR description doesn't meet our guidelines
  • Contribution appears to be AI-generated without meaningful review

If you believe this was a mistake, please open an issue explaining your intended contribution and a maintainer will help you.

@ralphstodomingo

ralphstodomingo commented Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

Review log — claims contract

Numbered falsifiable claims for scoped review; a finding is a reproducible trace violating a claim. Instances of the residuals are accepted trade-offs, not findings.

Claims

  • C1 — On desktop editors, a datamate stdio entry whose command is the editor's Electron binary is always spawned with ELECTRON_RUN_AS_NODE=1 in its environment, on every path this head can spawn it from (datamate_manager add, session-boot connect of a persisted entry, reload endpoint). No path spawns the entry's command without the flag once this head has written or healed the entry.
  • C2 — The only env key carried from any mcp.json into a persisted environment or a spawned child env is ELECTRON_RUN_AS_NODE. No other key from a repo file can reach either.
  • C3 — A file becomes a transport source only if its project-relative path ends in .vscode/mcp.json or .cursor/mcp.json and its canonical (realpath) target stays inside the project outside pruned dependency/build trees. No other file in a checkout can become the source for readDatamateTransportFromIde or the sync.
  • C4 — No automatic path (TUI/run/serve boot heal, reload endpoint) ever writes a GLOBAL config entry unless that entry already carries managedBy: "altimate-ide" with sourceMcpJson equal to the exact source file in hand. Hand-added and legacy global entries are never modified automatically.
  • C5 — An IDE entry without a non-empty string url (remote) or non-empty string command (local) is never selected as a source and never persisted; in particular, no type: "remote" entry lacking a url string is ever written to any config file.
  • C6resolveDatamateSyncRoot never returns $HOME (including $HOME reached through a symlink or differing case on win32) and never walks above it.
  • C7datamate_manager add on an existing entry re-derives only transport identity + enabled + updatedAt + provenance; user-managed fields (timeout, oauth, headers, …) survive byte-identical. The already-connected branch writes disk only and never touches the live client.
  • C8 — The multi-file heal is per-file isolated (a malformed config file yields a logged skip, not an aborted pass) and idempotent (a file is rewritten only when updatedAt or transport/provenance identity differs — no rewrite loops).
  • C9 — Behaviors inherited from main across the merge are preserved: the workspace-managed datamate key refusals in add/remove, and DiscoveryFiles' canonical-path rejection of symlinked IDE files.

Residuals

  • R1 — Legacy env-less GLOBAL entries do not auto-heal (deliberate boundary from the security review); the remedy is an explicit datamate_manager add --scope global, and the pop persists until then.

  • R2${VAR} env-reference resolution parity with generic discovery is not implemented; moot for the single allowlisted key, tracked as follow-up.

  • R3addMcpToConfig read-modify-write is not serialized across processes (pre-existing, tracked as follow-up).

  • R4 — The sync's change signal is updatedAt only; IDE entries without it are not synced (documented contract; the connected-add path now repairs such entries on explicit add).

  • R5 — A datamate entry duplicated across several loader-merged config filenames in one scope reconnects from the first match, not loader-merge precedence (unsupported layout).

  • R6.github/copilot/mcp.json is not a transport source (the extension never writes it; generic discovery still surfaces it for opt-in).

  • R7 — The boot heal's mcp.json scan runs on the run/TUI startup critical path (correctness-first; same cost serve has always paid).

  • R8 — The reload endpoint reads the healed entry via first match across covered config files (project before global; see R5 for the same-scope edge).

  • R9 — (workspace-mode interaction, analyzed 2026-09-08) The boot heal writes disk entries even while a workspace owns the datamate key. Runtime is unaffected: the workspace overlay derives in memory at config load after discovery with "the last word", every runtime writer refuses in workspace mode (reload endpoint 409s before the sync, datamate_manager add/remove refuse, mcp routes and session guard likewise — all preserved through the merge), and the heal is sequenced strictly before config load in every entry point, so it can never swap an engine under a session. The disk write matches serve's pre-existing ungated boot sync and keeps the dormant IDE-transport entry healed for when the workspace is unbound. Gating boot heals on managedWorkspace() would be a one-line change but alters serve's existing behavior too — deferred to the workspace line's owners. Workspace suites (423 tests incl. datamate-manager-workspace) green on this head.

  • R10sourceMcpJson is an absolute path: moving a project or syncing the global config to another machine silently breaks the provenance bond, and the entry falls back to documented R1 behavior (no auto-heal; explicit add restamps). Known support path.

Scoped codex rounds run against this comment; outcomes are appended here.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bdce4125cb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/opencode/src/server/server.ts Outdated
The reload endpoint's read-back scanned only the instance directory's config
files plus global, while the sync heals along the launch-directory-to-root
walk — a nested instance with the datamate entry in a root-level config
healed the file, then skipped the reconnect and reported updated anyway,
leaving the live client on the stale transport. The walk is now extracted as
collectDatamateHealPaths, shared by the sync and the endpoint so the two can
never disagree about where a healed entry lives. Source guard updated;
walk-parity regression test added.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GSk1Tcr4pH3aZRU4tVKrdi
@ralphstodomingo

Copy link
Copy Markdown
Contributor Author

@codex review against the numbered claims and the disclosed residuals in the review-log comment on this PR: report only a reproducible trace that violates a numbered claim. Instances of the disclosed residuals are not findings. A round with no claim violation ends review.

Comment thread packages/opencode/src/altimate/datamate-transport.ts Outdated
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. You're on a roll.

Reviewed commit: 04daa04088

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

…ved root in the heal walk

- A connected entry disabled on disk skipped the refresh write (the identity
  comparison omitted enabled), so an explicit add failed to persist the
  re-enable and the disable resurrected on restart. enabled joins the
  comparison.
- collectDatamateHealPaths accepts the caller's already-resolved root; the
  sync passes its own, removing a redundant bounded root walk per boot. The
  reload endpoint keeps the internal resolution.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GSk1Tcr4pH3aZRU4tVKrdi
Comment thread packages/opencode/src/altimate/tools/datamate.ts Outdated
sahrizvi
sahrizvi previously approved these changes Sep 8, 2026

@sahrizvi sahrizvi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving — with three items to fix

The rework closes everything raised in the previous round, and I verified each against the code rather than the description:

  • Env carry is now a real allowlist. SPAWN_ENV_ALLOWLIST is a single key, and the test seeds NODE_OPTIONS, LD_PRELOAD, PATH and ALTIMATE_EXTENSION_RPC and asserts only ELECTRON_RUN_AS_NODE survives. That is the right shape of test for this.
  • Provenance gating works as described — a hand-added global entry and one stamped from a different project's mcp.json both survive a project-local heal byte-identical.
  • parseIdeTransport closes the url-less-remote bug, with the exact failing fixture as a regression test.
  • collectDatamateHealPaths is shared by the sync and the reload read-back, so the two can no longer disagree about where a healed entry lives.
  • The scan is pruned and canonicalized via DiscoveryFiles, and restricted to the extension-written locations.
  • The new suite is behavioral throughout — real fixtures, real disk assertions.

The claims contract made this reviewable in a way the previous round was not. Three of the nine do not hold as written, and I would like them addressed — but none is a repeat of a prior blocker, and they are all small.


1. TRANSPORT_IDENTITY_FIELDS omits env, so the allowlist can be bypassed on a heal (C2)

The preserved loop carries any legacy env key forward verbatim. At load, config/config.ts:103 does transformed.environment = entry.env, and :104 then overwrites it from entry.environment. So when the healed entry has no environment key — i.e. the IDE entry supplied no allowlisted value, so extractSpawnEnvironment returned undefined and the key was omitted — the preserved env reaches mcp/index.ts:577 and is spread over process.env at spawn.

That is the allowlist being bypassed in precisely the legacy-healing path this PR exists to serve.

Fix: add "env" to TRANSPORT_IDENTITY_FIELDS. Worth a regression test that heals an entry carrying env.NODE_OPTIONS with no environment.

2. resolveDatamateSyncRoot returns $HOME when the launch directory is $HOME (C6)

Executed against the function:

home IS a git repo      -> returns $HOME
home is NOT a git repo  -> returns $HOME
subdirectory of home    -> returns the subdir   (what the current test covers)

Filesystem.up yields $HOME/.git; isSamePath correctly refuses it as root; control then falls through to return directory, which is $HOME. With no .git, nothing is yielded and the same fallthrough applies. The guard rejects $HOME as a walked-to root but not as the starting directory, and test:578 asserts only the subdirectory case — so it passes while the boundary is unguarded. Both run and the TUI worker pass process.cwd().

The consequence is the **/mcp.json glob running over the whole home tree, where an unrelated checkout's IDE entry can win source selection. That harm is the same whether home was reached from below or passed in directly.

Fix: the useful invariant is "$HOME is never a scannable project root" — skip the IDE scan and heal in that case, rather than a strict early return (which would lock out a user whose dotfiles project genuinely is $HOME). Add a test for resolveDatamateSyncRoot(home).

3. Scope dedup can downgrade the global config to project scope, skipping the provenance gate (C4)

Executed with launchDir === globalDir:

collectDatamateHealPaths(globalDir, globalDir)
  -> scope=project   <globalDir>/altimate-code.json

findProjectConfigPaths(launchDir) finds the physical global config first and tags it scope: "project"; findGlobalConfigPaths then finds the same path but seen suppresses it. healEntryInFile receives "project" and never applies the provenance check, so a hand-added global entry is auto-rewritten from a project-local mcp.json — the case C4 exists to prevent.

Fix: classify by canonical ownership (anything at or under the global config dir is global), or collect global paths first and let global win collisions.


Minor / follow-up

  • collectDatamateHealPaths uses dir.startsWith(rootResolved) — a lexical prefix match with no separator, so /home/u/pro would "contain" /home/u/projects/sub. It cannot trigger today because dir is always derived by path.dirname from launchDir, but rootResolved + path.sep is the sturdier form.
  • identityChanged re-lists managedBy/sourceMcpJson, which TRANSPORT_IDENTITY_FIELDS already contains.
  • TRANSPORT_IDENTITY_FIELDS now carries provenance fields too, so the name no longer describes its contents.
  • mergeRefreshedEntry carries Remote-only fields (headers, headersCommand, oauth) onto a local entry across a transport-type change. Ignored at decode, but it puts schema-invalid data on disk.
  • Disk-only fields (updatedAt, managedBy, sourceMcpJson) still reach MCP.add on the disconnected-refresh path, contradicting the comment that calls them disk-only.
  • sourceMcpJson is an absolute path, so moving a project or syncing the global config to another machine silently breaks the provenance bond and drops back to the documented R1 behavior. Worth adding to the residuals list so the support path is known.
  • The adversarial assertion in upi-config-mcp.test.ts:199-205 still matches on source text and was updated to track the refactor. Pre-existing pattern, but it now guards a security-relevant path, so converting it to a behavior test (seed a global-only entry, hit the reload endpoint, assert what MCP.add receives) would be worth more than the string match.

Approving on the strength of the rework; please pick these up before or shortly after merge.

…ot, global scope downgrade

- Legacy `env` joins TRANSPORT_IDENTITY_FIELDS: config load aliases `env` to
  `environment` when no `environment` key is present, so a preserved legacy
  `env` on a healed entry reached the spawn spread and bypassed the allowlist
  exactly on the legacy entries the heal exists to repair. Regression test
  heals an entry carrying env.NODE_OPTIONS and asserts both keys are gone.
- A launch root that IS the home directory now skips the heal entirely: the
  resolver's home rejection only covered walked-to roots, so launching from
  $HOME globbed the whole home tree where any unrelated checkout's IDE entry
  could win source selection. Only the automatic heal declines; explicit add
  still works for a dotfiles project at home.
- Heal candidates are scoped by canonical ownership, not discovery order: a
  file at or under the global config dir is global no matter which loop found
  it, so launching from that dir can no longer downgrade the physical global
  config to project scope and skip the provenance gate. The root containment
  check also uses a separator-anchored prefix.
- Folded minors from the same review: duplicate provenance keys removed from
  the connected-refresh comparison; disk-only fields (updatedAt, managedBy,
  sourceMcpJson) stripped from the config handed to MCP.add.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GSk1Tcr4pH3aZRU4tVKrdi
@ralphstodomingo

Copy link
Copy Markdown
Contributor Author

Thank you for executing the claims rather than reading the descriptions — all three findings verified exactly as your traces show, fixed in 63b489a:

  1. C2 / legacy env alias: env joins TRANSPORT_IDENTITY_FIELDS with the aliasing rationale in the doc comment; regression test heals an entry carrying env.NODE_OPTIONS against an IDE entry with no allowlisted value and asserts both env and environment are gone from the healed entry.
  2. C6 / home as launch root: the sync now declines when the resolved root IS home (isSamePath, so the symlink/casing canonicalization applies here too) — scan and heal are skipped, explicit add remains available for a dotfiles project at home. Tests cover resolveDatamateSyncRoot(home) (documented fallback) and the sync writing nothing from home.
  3. C4 / scope downgrade: candidates are scoped by canonical ownership — anything at or under the global config dir is global regardless of which loop found it. Test launches from the global dir itself and asserts the tag plus a hand-added entry surviving byte-identical.

Folded from your minors in the same commit: separator-anchored root containment, duplicate provenance keys removed from the connected-refresh comparison, and disk-only fields stripped from the config handed to MCP.add (the comment is now true).

Deferred, with reasons: the TRANSPORT_IDENTITY_FIELDS rename (exported symbol, churn > value now that the doc comment states its contents), the cross-type Remote-field carry in mergeRefreshedEntry (schema-ignored at decode; follow-up), and converting the adversarial source-text guard to a behavior test (follow-up — agreed it's worth more). sourceMcpJson portability is now R10 on the review log per your suggestion.

Your four 08-26 threads: resolving them on the strength of your "closes everything raised in the previous round" — shout if you'd rather any stay open.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 3 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread packages/opencode/src/altimate/datamate-transport.ts Outdated
Comment thread packages/opencode/src/altimate/datamate-transport.ts
Comment thread packages/opencode/src/altimate/tools/datamate.ts
Comment thread packages/opencode/src/altimate/datamate-transport.ts Outdated
A symlink alias (or Windows casing) of the global config dir made the lexical
ownership check tag the physical global config as project scope, skipping the
provenance gate. Ownership is now decided on canonical paths via the shared
canonicalizer that already backs the home-root rejection; regression test
launches from the real dir with the alias configured as the global dir. The
root-containment expression also drops its redundant clause.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GSk1Tcr4pH3aZRU4tVKrdi

@sahrizvi sahrizvi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving — all three findings verified fixed

I re-ran the same probes that produced the original traces, against this head rather than reading the fix descriptions. All three behave correctly now:

Legacy env alias (C2) — persisted entry carrying env: {NODE_OPTIONS: "--require /tmp/payload.js"}, healed against an IDE entry with no allowlisted key:

healed entry keys: type, command, updatedAt, managedBy, sourceMcpJson
  env present?         no
  environment present? no

Home as scannable root (C6) — the sync now declines before scanning, in both the git and non-git cases:

home IS git repo:  sync from home -> updated=[]  config unchanged? YES (declined)
home NOT git repo: sync from home -> updated=[]  config unchanged? YES (declined)

Keeping resolveDatamateSyncRoot(home) === home as a documented fallback and enforcing the invariant at the sync boundary is the better shape — a dotfiles project at home keeps the explicit add path.

Global scope downgrade (C4) — launching from the global config dir, with an unstamped hand-added entry:

scope=global  ~/.config/altimate-code/altimate-code.json
hand-added global entry byte-identical after heal? YES (protected)

Previously this printed scope=project.

The three folded minors are all in: separator-anchored containment, the deduplicated identityChanged comparison, and disk-only fields stripped before MCP.add so that comment is finally accurate. The four new tests are behavioral and would fail against the previous head, which is the property that matters. I tried to break the new ownership check (symlinked global dir, global dir nested in the project, launch dir at the global dir) and the home decline (symlinked home, home with and without .git) without success.


Two follow-ups — neither blocks this

1. The containment fix I suggested last round has a filesystem-root edge. !dir.startsWith(rootResolved + path.sep): when rootResolved is /, the prefix becomes "//", which nothing matches, so the ancestor walk stops at the launch directory.

rootResolved='/'     dir='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/repo/pkg' -> walks ancestors? false
rootResolved='/repo' dir='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/repo/pkg' -> walks ancestors? true

The previous unanchored form handled / correctly, so anchoring it traded one edge for another — that is on my earlier suggestion, not on you. path.relative(rootResolved, dir) handles both: contained when the result is neither absolute nor ..-prefixed. Reaching it needs .git at / or a launch at /, so it is not urgent, but a POSIX-root case in the containment tests would pin it.

2. The home decline also stops the non-datamate remote URL refresh. The early return sits before the transport scan, so the second pass that refreshes URLs for other remote MCP entries never runs from home:

launched from HOME:
  updated = []
  othersrv URL refreshed? NO

Reasonable as written — both passes go through findAllMcpJsonFiles(root), and the recursive home scan is exactly what the guard prevents. Worth noting only because that pass is documented as deliberate behavior, and it is recoverable without reopening the hole: reading just $HOME/.vscode/mcp.json and $HOME/.cursor/mcp.json for the non-datamate pass is two stat calls rather than a tree walk. Your call whether that is worth the branch; if you keep the current behavior, the comment reads narrower than what the code does, since it declines the whole sync rather than only the heal.

3. Nitseen in collectDatamateHealPaths dedups on the raw path while scope is decided canonically, so one physical file reachable under two lexical paths gets queued twice. Scope agrees and the heal is idempotent, so the cost is a redundant read.

Thanks for the claims contract and the residuals list — being able to test numbered assertions instead of inferring intent is what made the last two rounds quick and specific.

@ralphstodomingo
ralphstodomingo merged commit 1bbf453 into main Sep 8, 2026
22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

datamate-cli.js opens as an editor tab when launching sessions (stdio MCP spawn loses ELECTRON_RUN_AS_NODE)

3 participants